"""
身份证验证系统 - 异步队列处理版本

功能说明：
1. 接收用户提交的身份证照片（上传到七牛云）
2. 将验证任务放入Redis队列（异步处理，立即返回）
3. 定时任务从队列取出任务，执行OCR识别
4. 通过WebSocket实时通知用户验证结果

流程：
用户提交 → 上传七牛云 → 任务入队 → 定时任务处理 → OCR识别 → 更新数据库 → WebSocket通知
"""

import asyncio
import base64
import json
import os
import time
import urllib.parse
from concurrent.futures import ThreadPoolExecutor  # 用于在异步环境中执行同步的七牛云上传操作
from typing import List, Dict
from typing import Optional

import httpx  # 异步HTTP客户端，用于OCR API调用和图片下载
import redis.asyncio as redis_async  # 异步Redis客户端
import requests  # 同步HTTP客户端，用于获取百度access_token
from apscheduler.schedulers.asyncio import AsyncIOScheduler  # 异步调度器，用于定时任务
from fastapi import APIRouter
from fastapi import Form, File, UploadFile, Header, HTTPException
from qiniu import Auth, put_data  # 七牛云SDK
from starlette.websockets import WebSocket

from models.m import User, AuthorVerification, AuthorVerificationStatus, UserRole
from tool.jwt_utils import get_current_user

# ==================== 路由初始化 ====================
# 创建FastAPI路由实例，用于注册接口和WebSocket端点
list_rz_wedSockert = APIRouter()

# ==================== Redis连接 ====================
# 创建异步Redis连接，用于任务队列存储
# 队列名称：id_card_verify_queue
# 使用列表结构：lpush（左推入）用于入队，brpop（阻塞右弹出）用于出队
redis_conn = redis_async.Redis(host='127.0.0.1', port=6379, db=0)

# ==================== 百度OCR配置 ====================
# 用于身份证OCR识别的API密钥
API_KEY = "LUEeCmxq5nifS0yxzBR9m5J2"
SECRET_KEY = "2TnBiY0o0ZzGRBUzvmig6liXaGsv66xg"

# ==================== 七牛云配置 ====================
# 用于存储身份证照片的云存储配置
QINIU_ACCESS_KEY = 'IPzQQFeLOA4fPDWzUTQQX7eJ4OcAfyzDyR3A3HN6'
QINIU_SECRET_KEY = 'hi1t3IWhYxp5H-SI7sJnFLHR9x_oUt8mFeYtuz6z'
QINIU_BUCKET = '653ef'  # 存储桶名称
QINIU_DOMAIN = 'http://t4y377zln.hb-bkt.clouddn.com'  # 七牛云域名

# ==================== 调度器配置 ====================
# 异步调度器，用于定时执行任务处理函数
scheduler = AsyncIOScheduler()

# ==================== 任务重试配置 ====================
# 最大重试次数：如果任务处理失败，最多重试1次
MAX_RETRY_COUNT = 1

# ==================== 启动标志 ====================
# 防止应用启动时重复初始化调度器
_scheduler_started = False


# ==================== 分布式锁超时增加时间 ====================

async def lock_renewer(lock_key: str, stop_event: asyncio.Event, redis_conn: redis_async.Redis):
    """
    增加分布式锁的过期时间

    Args:
        timeout (int): 原始过期时间（秒）

    Returns:
        int: 新的过期时间（秒）
    """
    while not stop_event.is_set():
        try:
            ttl = await redis_conn.ttl(lock_key)

            if ttl <= 0 or stop_event.is_set():
                break
            if ttl < 10:
                await redis_conn.expire(lock_key, 30)
                print(f"{lock_key} 分布式锁续期成功，重置为30")
        except Exception as e:
            print(f"分布式锁续期失败: {str(e)}")

        await asyncio.sleep(5)


# ==================== 模块1：百度OCR识别工具 ====================

async def get_access_token():
    """
    获取百度OCR API的access_token
    
    Returns:
        str: access_token，用于后续OCR接口调用
        None: 获取失败时返回None
    
    说明：
        - access_token有效期通常为30天
        - 每次调用OCR接口前都需要先获取token
    """
    url = "https://aip.baidubce.com/oauth/2.0/token"
    params = {
        "grant_type": "client_credentials",
        "client_id": API_KEY,
        "client_secret": SECRET_KEY
    }
    try:
        response = requests.post(url, params=params)
        result = response.json()
        return result.get("access_token")
    except Exception as e:
        print(f"获取token失败: {str(e)}")
        return None


async def ocr_id_card(img_url):
    """
    从七牛云URL识别身份证正面信息
    
    Args:
        img_url (str): 身份证照片的URL（七牛云链接）
    
    Returns:
        dict: OCR识别结果，包含：
            - words_result: 识别出的字段（姓名、身份证号等）
            - image_status: 图片质量状态（normal/over_exposure等）
            - log_id: 请求日志ID
    
    Raises:
        Exception: 下载图片失败、图片过大、OCR调用失败时抛出异常
    
    流程：
        1. 从URL下载图片
        2. 检查图片大小（≤5MB）
        3. Base64编码图片
        4. 调用百度OCR API识别
        5. 返回识别结果
    """
    # 步骤1：从URL下载图片
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(img_url, timeout=10)
            resp.raise_for_status()  # 检查HTTP状态码
            img_data = resp.content
    except Exception as e:
        raise Exception(f"下载图片失败: {str(e)}")

    # 步骤2：检查图片大小（最大5MB）
    if len(img_data) > 5 * 1024 * 1024:
        raise Exception("图片大小超过5MB限制")

    # 步骤3：Base64编码（OCR API要求）
    img_base64 = base64.b64encode(img_data).decode("utf-8")

    # 步骤4：获取access_token并调用OCR接口
    access_token = await get_access_token()
    if not access_token:
        raise Exception("获取access_token失败")

    url = f"https://aip.baidubce.com/rest/2.0/ocr/v1/idcard?access_token={access_token}"
    payload = {
        'image': img_base64,
        'id_card_side': 'front',  # 识别身份证正面
        'detect_quality': 'true'  # 启用质量检测（会返回image_status）
    }
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}

    try:
        async with httpx.AsyncClient() as client:
            respone = await client.post(url, headers=headers, data=urllib.parse.urlencode(payload))
            result = respone.json()
            print(f"OCR结果: {result}")
            return result
        # 下方代码永远不会执行（已注释，可删除）
        return response.json()
    except Exception as e:
        raise Exception(f"OCR接口调用失败: {str(e)}")


# ==================== 模块2：作者注册接口（仅负责入队） ====================

@list_rz_wedSockert.post("/register", summary="注册作者", tags=["用户"])
async def register_author(
        pen_name: str = Form(...),
        introduction: Optional[str] = Form(None),
        sample_work: Optional[str] = Form(None),
        id_card_front_file: UploadFile = File(...),
        id_card_back_file: Optional[UploadFile] = File(None),
        authorization: str | None = Header(None)):
    """
    作者注册接口 - 接收用户提交的认证信息
    
    功能：
        - 验证用户身份
        - 上传身份证照片到七牛云
        - 将验证任务放入Redis队列
        - 立即返回（不等待OCR处理）
    
    Args:
        pen_name (str): 笔名（必填）
        introduction (Optional[str]): 作者简介（可选）
        sample_work (Optional[str]): 作品样例（可选）
        id_card_front_file (UploadFile): 身份证正面照片（必填）
        id_card_back_file (Optional[UploadFile]): 身份证反面照片（可选）
        authorization (str): Bearer Token（用于身份验证）
    
    Returns:
        dict: 
            {
                "code": 200,
                "message": "申请已提交，正在审核中",
                "data": {"verification_id": None}
            }
    
    Raises:
        HTTPException: 
            - 401: Token无效或用户不存在
            - 400: 已提交过申请、文件格式不支持
            - 500: 文件上传失败
    
    流程：
        1. 验证Token并获取用户ID
        2. 检查是否已提交过申请
        3. 上传身份证正面到七牛云
        4. 上传身份证反面到七牛云（可选）
        5. 构造任务数据并推送到Redis队列
        6. 立即返回成功响应
    """
    # ===== 步骤1：验证Token并获取用户 =====
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="缺少Bearer Token")

    token = authorization.split(" ", 1)[1].strip()  # 提取Token（去掉"Bearer "前缀）
    user_id = await get_current_user(token)  # 从Token中解析用户ID

    # 验证用户是否存在
    if not user_id or not await User.exists(id=user_id):
        raise HTTPException(status_code=401, detail="无效用户")

    # ===== 步骤2：检查是否已提交过申请 =====
    # 防止用户重复提交认证申请
    if await AuthorVerification.exists(user_id=user_id):
        raise HTTPException(status_code=400, detail="已提交过认证申请")

    # ===== 步骤3：上传身份证正面到七牛云 =====
    # 允许的文件格式
    allowed_ext = {'.jpg', '.jpeg', '.png'}
    front_ext = os.path.splitext(id_card_front_file.filename)[1].lower()

    # 验证文件格式
    if front_ext not in allowed_ext:
        raise HTTPException(status_code=400, detail="正面照片仅支持jpg/png")

    # 生成唯一文件名：front_{user_id}_{时间戳}.{扩展名}
    front_filename = f"front_{user_id}_{int(time.time())}{front_ext}"

    try:
        # 读取文件内容
        file_data = await id_card_front_file.read()

        # 检查文件大小（最大5MB）
        if len(file_data) > 5 * 1024 * 1024:
            raise Exception("图片大小超过5MB限制")

        # 构建七牛云上传凭证
        q = Auth(QINIU_ACCESS_KEY, QINIU_SECRET_KEY)
        token = q.upload_token(QINIU_BUCKET, front_filename, 3600)  # token有效期1小时

        # 使用线程池执行同步的七牛云上传操作（put_data是同步函数）
        executor = ThreadPoolExecutor()
        ret, info = await asyncio.get_event_loop().run_in_executor(
            executor,
            put_data,  # 七牛云上传函数
            token,
            front_filename,
            file_data
        )

        # 检查上传结果
        if info.status_code != 200:
            raise Exception("上传失败")

        # 构造七牛云URL
        front_url = f"{QINIU_DOMAIN}/{front_filename}"

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"正面照片上传失败: {str(e)}")

    # ===== 步骤4：上传身份证反面（可选） =====
    # 反面照片上传失败不影响主流程
    back_url = None
    if id_card_back_file:
        back_ext = os.path.splitext(id_card_back_file.filename)[1].lower()

        if back_ext not in allowed_ext:
            raise HTTPException(status_code=400, detail="反面照片仅支持jpg/png")

        back_filename = f"back_{user_id}_{int(time.time())}{back_ext}"

        try:
            file_data = await id_card_back_file.read()
            token = q.upload_token(QINIU_BUCKET, back_filename, 3600)

            ret, info = await asyncio.get_event_loop().run_in_executor(
                executor,
                put_data,
                token,
                back_filename,
                file_data
            )

            if info.status_code == 200:
                back_url = f"{QINIU_DOMAIN}/{back_filename}"
        except Exception as e:
            # 反面照片失败不阻断主流程，只记录错误
            print(f"反面照片上传失败: {str(e)}")

    # ===== 步骤5：构造任务数据 =====
    # 准备推送到Redis队列的任务数据
    verification_data = {
        "user_id": user_id,  # 用户ID
        "pen_name": pen_name,  # 笔名
        "introduction": introduction,  # 作者简介
        "sample_work": sample_work,  # 作品样例
        "id_card_front_url": front_url,  # 正面照片URL
        "id_card_back_url": back_url,  # 反面照片URL（可能为None）
        "status": int(AuthorVerificationStatus.PENDING),  # 状态：待处理
        "real_name": None,  # OCR识别出的真实姓名（待填充）
        "id_card": None,  # OCR识别出的身份证号（待填充）
    }

    # ===== 步骤6：任务入队（推送到Redis队列） =====
    # lpush: 从队列左侧推入（FIFO：先进先出）
    # 注意：此时任务还没有被处理，只是存入了队列
    await redis_conn.lpush("id_card_verify_queue", json.dumps(verification_data))

    # ===== 步骤7：立即返回响应 =====
    # 不等待OCR处理完成，立即返回给前端
    # OCR处理将由定时任务异步完成
    return {
        "code": 200,
        "message": "申请已提交，正在审核中",
        "data": {"verification_id": None}
    }


# ==================== 模块3：WebSocket实时通信 ====================

# WebSocket连接管理
active_connections: List[WebSocket] = []  # 所有活跃的WebSocket连接列表
client_connections: Dict[str, WebSocket] = {}  # 按用户ID索引的连接字典 {user_id: websocket}


async def send_to_client(client_id: str, message: str):
    """
    向指定客户端发送WebSocket消息
    
    Args:
        client_id (str): 客户端ID（通常是用户ID）
        message (str): 要发送的消息内容
    
    说明：
        - 用于发送验证结果通知（成功/失败）
        - 如果客户端未连接，则静默失败（不抛出异常）
    """
    if client_id in client_connections:
        await client_connections[client_id].send_text(message)


@list_rz_wedSockert.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    """
    WebSocket连接端点
    
    功能：
        - 建立与客户端的WebSocket连接
        - 保持连接以便随时发送验证结果通知
    
    Args:
        websocket (WebSocket): WebSocket连接对象
        client_id (str): 客户端ID（通常是用户ID，从URL路径获取）
    
    流程：
        1. 接受WebSocket连接
        2. 将连接保存到管理字典中
        3. 阻塞等待客户端消息（维持连接）
        4. 连接断开时清理连接记录
    """
    # 接受连接
    await websocket.accept()

    # 保存连接
    active_connections.append(websocket)  # 添加到所有连接列表
    client_connections[client_id] = websocket  # 按用户ID索引保存

    print(f"客户端 {client_id} 已连接，当前连接数：{len(active_connections)}")

    try:
        # 维持连接：阻塞等待客户端消息
        # 即使客户端不发送消息，连接也会保持
        # 直到客户端主动断开或网络异常
        while True:
            await websocket.receive_text()  # 无消息时阻塞，直到连接断开
    except Exception as e:
        # 客户端断开时清理连接
        if client_id in client_connections:
            del client_connections[client_id]
        if websocket in active_connections:
            active_connections.remove(websocket)
        print(f"客户端 {client_id} 已断开，当前连接数：{len(active_connections)}")


# ==================== 模块4：定时任务（处理身份证验证） ====================

async def process_verify_queue():
    """
    处理Redis队列中的身份证验证任务
    
    功能：
        - 从Redis队列取出任务
        - 执行OCR识别
        - 更新数据库记录
        - 发送WebSocket通知
    
    执行频率：
        - 每15秒执行一次（由调度器控制）
    
    流程：
        1. 检查Redis连接
        2. 循环从队列取任务（阻塞等待最多10秒）
        3. 检查重试次数
        4. 检查是否已处理
        5. 执行OCR识别
        6. 检查图片质量
        7. 提取并验证数据
        8. 创建验证记录并更新用户角色
        9. 发送成功通知
        10. 异常处理：重试或创建拒绝记录
    
    注意：
        - brpop是原子操作，取出后任务会立即从队列中删除
        - 如果处理失败且未超过重试次数，会重新入队
        - 如果超过重试次数，会创建拒绝记录并通知用户
    """
    # ===== 步骤1：检查Redis连接 =====
    print("开始定时任务作者运行处理身份证验证任务")
    try:
        await redis_conn.ping()  # 测试连接是否正常
    except Exception as e:
        print(f"Redis连接失败: {str(e)}")
        return  # 连接失败，退出本次执行

        # ===== 步骤2：单次处理一个任务（移除持续循环） =====
    try:
        verification = None
        task = None  # 初始化task变量（用于异常处理）

        # 从队列右侧取出任务（brpop：阻塞右弹出）
        # timeout=10：如果没有任务，阻塞等待10秒，超时则返回None
        # 从原队列取出并放入处理中队列
        result = await redis_conn.rpoplpush("id_card_verify_queue", "processing_queue")

        # # 注意：brpop是原子操作，取出后数据会立即从队列中删除
        # result = await redis_conn.brpop(["id_card_verify_queue"], timeout=10)

        # 如果没有任务，直接返回（等待下次调度）
        if not result:
            return

        # 解析结果：result格式为 (队列名, 任务数据)
        task_str = result

        # ===== 步骤3：解析任务JSON =====
        try:
            task = json.loads(task_str)
        except json.JSONDecodeError as e:
            print(f"任务JSON解析失败: {str(e)}")
            return  # 跳过并结束本次执行

        # ===== 步骤4：检查重试次数 =====
        retry_count = task.get("retry_count", 0)

        if retry_count >= 1:
            print(f"任务超过最大重试次数，跳过: user_id={task.get('user_id', 'unknown')}")

            # 创建拒绝记录
            try:
                await AuthorVerification.create(
                    user_id=task.get("user_id"),
                    pen_name=task.get("pen_name", ""),
                    introduction=task.get("introduction"),
                    sample_work=task.get("sample_work"),
                    id_card_front_url=task.get("id_card_front_url"),
                    id_card_back_url=task.get("id_card_back_url"),
                    status=AuthorVerificationStatus.REJECTED,
                    real_name=None,
                    id_card=None,
                    reject_reason=f"超过最大重试次数({MAX_RETRY_COUNT}次)"
                )
                # 通知用户验证失败
                await send_to_client(
                    client_id=str(task.get("user_id")),
                    message="验证失败：超过最大重试次数"
                )

            except:
                pass  # 创建记录失败也不影响，记录日志即可

            return  # 结束本次执行

        # ===== 步骤5：获取任务数据 =====
        user_id = task["user_id"]
        front_url = task["id_card_front_url"]

        # ===== 步骤6.5：获取分布式锁（防止并发处理） =====
        # 在OCR和创建记录期间加锁，确保同一用户只有一个任务在处理
        # lock_key = f"verify_lock_{user_id}"
        # lock_acquired = False
        # renewer_task = None  # 用于存储守护任务对象
        # stop_event = asyncio.Event()  # 用于通知守护任务停止的事件

        try:
            # 尝试获取锁，超时时间30秒（足够完成OCR和创建）
            # lock_acquired = await redis_conn.set(
            #     lock_key,
            #     "1",
            #     ex=30,  # 30秒过期
            #     nx=True  # 只有key不存在时才设置（原子操作）
            # )
            #
            # if not lock_acquired:
            #     # 锁已被其他任务占用，跳过这个任务（说明正在处理中）
            #     print(f"[跳过] 用户 {user_id} 的验证任务正在处理中，跳过")
            #     return

            # 获取锁成功，启动守护任务（30秒后自动续期,如果剩余10秒还未结束自动将时间重置威30秒）
            # renewer_task = asyncio.create_task(lock_renewer(lock_key, stop_event,redis_conn))

            # ===== 步骤7：执行OCR识别 =====
            print(f"开始识别开始处理用户 {user_id} 的身份证验证任务=====================================================================")
            ocr_result = await ocr_id_card(front_url)

            # ===== 步骤8：检查图片质量状态 =====
            image_status = ocr_result.get("image_status", "")
            quality_errors = {
                "over_exposure": "曝光过度",
                "under_exposure": "曝光不足",
                "blurred": "图像模糊"
            }

            # 如果有质量问题，抛出异常（会触发重试机制）
            if image_status in quality_errors:
                raise Exception(f"照片质量问题: {quality_errors[image_status]}")
            if image_status not in ["normal", ""]:
                raise Exception(f"无效照片状态: {image_status}")

            print(ocr_result)

            # ===== 步骤9：提取姓名和身份证号 =====
            words = ocr_result.get("words_result", {})
            real_name = words.get("姓名", {}).get("words", "").strip()
            id_card_num = words.get("公民身份号码", {}).get("words", "").strip()

            # ===== 步骤10：验证数据完整性 =====
            if not real_name:
                raise Exception("真实姓名不能为空")
            if not id_card_num or len(id_card_num) != 18:
                raise Exception("身份证号格式无效")

            # # ===== 步骤11：创建验证记录 =====
            # # 先检查记录是否已存在（防止重复创建）
            # verification = await AuthorVerification.get_or_none(user_id=user_id)
            # if verification:
            #     print(f"验证记录已存在: user_id={user_id}, 跳过创建")
            #     return

            # 尝试创建新记录
            try:
                verification = await AuthorVerification.create(
                    user_id=user_id,
                    pen_name=task["pen_name"],
                    introduction=task["introduction"],
                    sample_work=task["sample_work"],
                    id_card_front_url=front_url,
                    id_card_back_url=task["id_card_back_url"],
                    status=AuthorVerificationStatus.APPROVED,  # 直接设为已批准
                    real_name=real_name,  # OCR识别出的真实姓名
                    id_card=id_card_num  # OCR识别出的身份证号
                )
            except Exception as e:
                # 如果创建失败（可能是唯一约束冲突），再次检查
                error_msg = str(e).lower()
                if "duplicate" in error_msg or "1062" in error_msg:
                    # 可能是并发创建导致的，重新检查一下
                    verification = await AuthorVerification.get_or_none(user_id=user_id)
                    if verification:
                        print(f"记录已存在（并发检测）: user_id={user_id}, 跳过")
                        return
                # 其他错误继续抛出
                raise e

            # ===== 步骤12：更新用户角色为作者 =====
            user = await User.get_or_none(id=user_id)
            if not user:
                raise Exception("用户不存在")

            # 使用枚举值更新角色（推荐方式）
            old_role = user.role  # 记录旧角色，用于日志
            user.role = UserRole.AUTHOR  # 设置为作者角色（枚举值：2）
            await user.save(update_fields=['role'])  # 明确指定更新字段，确保保存成功

            # 处理完成后手动删除
            await redis_conn.lrem("processing_queue", 1, task_str)

            # 验证更新是否成功
            await user.refresh_from_db()  # 从数据库重新加载
            if user.role != UserRole.AUTHOR:
                print(
                    f"[警告] 用户角色更新失败: user_id={user_id}, 期望角色={UserRole.AUTHOR}, 实际角色={user.role}")
            else:
                print(f"[成功] 用户角色已更新: user_id={user_id}, 从 {old_role} 更新为 {user.role}")

            # verification.save() 不需要，因为 create() 已经自动保存了

            print(f"验证通过: 用户{user_id}, 姓名{real_name}")

            # ===== 步骤13：发送成功通知 =====

            await send_to_client(client_id=str(user_id), message="验证成功")


        finally:
            return
            # ===== 释放分布式锁 =====
            # if lock_acquired:
            #     try:
            #         await redis_conn.delete(lock_key)
            #     except:
            #         pass  # 锁释放失败不影响主流程

    except Exception as e:
        # ===== 异常处理：重试或创建拒绝记录 =====
        await redis_conn.lrem("processing_queue", 1, task_str)

        await AuthorVerification.create(
            user_id=user_id,
            pen_name=task["pen_name"],
            introduction=task["introduction"],
            sample_work=task["sample_work"],
            id_card_front_url=front_url,
            id_card_back_url=task["id_card_back_url"],
            status=AuthorVerificationStatus.REJECTED,  # 设置为拒绝状态
            reason=str(e)  # 保存错误信息
        )
        AuthorVerification.save()
        return  # 结束本次执行


# ==================== 模块5：定时任务调度配置 ====================

def add_jobs():
    """
    添加定时任务到调度器
    
    功能：
        - 将process_verify_queue函数注册为定时任务
        - 每15秒执行一次
        - 如果任务已存在则跳过（避免重复添加）
    """
    if not scheduler.get_job("list_rz_wedSockert_id"):
        scheduler.add_job(
            func=process_verify_queue,  # 要执行的函数
            trigger="interval",  # 间隔触发
            seconds=60,  # 每10秒执行一次
            id="list_rz_wedSockert_id",  # 任务唯一标识
            replace_existing=True,  # 如果任务已存在则替换
            max_instances=10  # 允许最多3个实例同时运行（解决"maximum number of running instances reached"错误）
        )


@list_rz_wedSockert.on_event("startup")
def startup_event():
    """
    应用启动事件处理函数
    
    功能：
        - 应用启动时初始化调度器
        - 添加定时任务
        - 启动调度器
    
    注意：
        - 使用全局标志防止重复执行
        - 检查调度器状态避免重复启动
    """
    global _scheduler_started

    # 防止重复执行（使用调度器运行状态 + 全局标志双重检查）
    if _scheduler_started or scheduler.running:
        return  # 已启动，跳过

    _scheduler_started = True
    print("身份证验证开始")

    # 添加任务（如果不存在）
    if not scheduler.get_job("list_rz_wedSockert_id"):
        add_jobs()

    # 启动调度器
    scheduler.start()
    print("身份证验证调度器已启动")


@list_rz_wedSockert.on_event("shutdown")
def shutdown_event():
    """
    应用关闭事件处理函数
    
    功能：
        - 应用关闭时优雅地停止调度器
        - 确保所有任务执行完毕后再关闭
    """
    scheduler.shutdown()
    print("身份证验证结束")
